package io.github.libkodi.basic_gateway.tools;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.github.libkodi.basic_gateway.ExtendVerticle;
import io.github.libkodi.basic_gateway.annotations.Ignore;
import io.github.libkodi.basic_gateway.component.Component;
import io.github.libkodi.basic_gateway.component.ComponentMethod;
import io.github.libkodi.basic_gateway.component.ComponentObject;
import io.github.libkodi.basic_gateway.component.Components;
import io.github.libkodi.basic_gateway.confituration.Configurater;
import io.github.libkodi.basic_gateway.context.Context;
import io.github.libkodi.basic_gateway.router.Route;
import io.github.libkodi.basic_gateway.router.RouteIntercepter;
import io.github.libkodi.basic_gateway.router.RouteIntercepterImpl;
import io.github.libkodi.basic_gateway.router.RouterConfigurater;
import io.github.libkodi.basic_gateway.websocket.WebSocket;
import io.github.libkodi.basic_gateway.websocket.WebSocketEndPoint;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Verticle;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.ext.web.Router;

/**
 * 类管理器
 * 用于统一保存和分类各种扫描到的类 
 */
public class ClassFilter {
	public static final Logger log = LoggerFactory.getLogger(ClassFilter.class);
	public static final String name = "classFilter";
	private ClassLoader loader;
	private ArrayList<Class<?>> routes = new ArrayList<Class<?>>();
	private ArrayList<Class<?>> routeIntercepters = new ArrayList<Class<?>>();
	private ArrayList<Class<?>> configuraters = new ArrayList<Class<?>>();
	private ArrayList<Class<?>> routerConfiguraters = new ArrayList<Class<?>>();
	private ArrayList<Class<?>> verticles = new ArrayList<Class<?>>();
	private HashMap<String, ComponentObject> components = new HashMap<String, ComponentObject>();
	private HashMap<String, Class<?>> websockets = new HashMap<String, Class<?>>();
	private Context context;

	public ClassFilter(ClassLoader loader, Context ctx) {
		this.loader = loader;
		this.context = ctx;
	}
	
	/**
	 * 扫描到的类的统一入口
	 * 将扫描到的类统一依据该方法加载并分类保存
	 * @param name 类名
	 * @throws Throwable 
	 */
	public void add(String name) throws Throwable {
		Class<?> cls = loader.loadClass(name);
		
		if (!cls.isAnnotationPresent(Ignore.class)) {
			if (Route.class.isAssignableFrom(cls)) {
				routes.add(cls);
			} else if (Configurater.class.isAssignableFrom(cls)) {
				configuraters.add(cls);
			} else if (Component.class.isAssignableFrom(cls)) {
				components.put(name, new ComponentObject(cls));
			} else if (Components.class.isAssignableFrom(cls)) {
				Object instance = ClassUtils.newInstance(cls);
				Method initMethod = Components.class.getDeclaredMethod("init");
				
				Future<Void> res = ClassUtils.injectContext(instance, context);
				
				if (!res.succeeded()) {
					throw res.cause();
				}
				
				initMethod.invoke(instance);
				
				Method[] methods = cls.getDeclaredMethods();
				
				for (Method method : methods) {
					if (method.isAnnotationPresent(ComponentMethod.class)) {
						ComponentMethod cm = method.getAnnotation(ComponentMethod.class);
						Class<?> rtcls = method.getReturnType();
						String key = cm.value();
						
						if ("".equals(key)) {
							key = rtcls.getName();
						}
						
						components.put(key, new ComponentObject(cls, instance, method));
					}
				}
			} else if (RouteIntercepter.class.isAssignableFrom(cls)) {
				routeIntercepters.add(cls);
			} else if (WebSocket.class.isAssignableFrom(cls) && cls.isAnnotationPresent(WebSocketEndPoint.class)) {
				WebSocketEndPoint anno = cls.getAnnotation(WebSocketEndPoint.class);
				String path = anno.value();
				websockets.put(path, cls);
			} else if (ExtendVerticle.class.isAssignableFrom(cls)) {
				verticles.add(cls);
			} else if (RouterConfigurater.class.isAssignableFrom(cls)) {
				routerConfiguraters.add(cls);
			}
		}
	}
	
	/**
	 * 执行配置器类
	 * @param ctx 上下文
	 * @return {@link Future<Void>}
	 */
	public Future<Void> execConfiguration(Context ctx) {
		return Future.future(promise -> {
			if (configuraters.size() < 1) {
				promise.complete();
			} else {
				AnnoationUtils.sortByOrder(configuraters);
				Iterator<Class<?>> iter = configuraters.iterator();
				
				try {
					Method method = Configurater.class.getDeclaredMethod("init");
					eachExecConfiguraters(ctx, iter, method, promise);
				} catch (Exception e) {
					promise.fail(e);
				}
			}
		});
	}
	
	/**
	 * 遍历执行各个配置器
	 * @param ctx 上下文
	 * @param iter 遍历指针
	 * @param method 配置器初始化方法
	 * @param promise 
	 */
	@SuppressWarnings("unchecked")
	private void eachExecConfiguraters(Context ctx, Iterator<Class<?>> iter, Method method, Promise<Void> promise) {
		if (iter.hasNext()) {
			try {
				Class<?> cls = iter.next();
				Object instance = ClassUtils.newInstance(cls);
				
				ClassUtils.injectContext(instance, ctx).onSuccess(v -> {
					try {
						Future<Void> ret = (Future<Void>) method.invoke(instance);
						
						ret.onSuccess(v1 -> {
							eachExecConfiguraters(ctx, iter, method, promise);
						}).onFailure(e -> {
							promise.fail(e);
						});
					} catch(Exception e) {
						promise.fail(e);
					}
				}).onFailure(e -> {
					promise.fail(e);
				});
			} catch (Exception e) {
				promise.fail(e);
			}
		} else {
			promise.complete();
		}
	}
	
	/**
	 * 依据类名查找类
	 * @param className 类名 
	 * @return {@link Class}
	 */
	public ComponentObject getComponentClass(String className) {
		if (components.containsKey(className)) {
			return components.get(className);
		} else {
			return null;
		}
	}
	
	public Future<Void> initAllComponent(Context ctx) {
		return Future.future(promise -> {
			if (components.size() < 1) {
				promise.complete();
			} else {
				try {
					Iterator<Entry<String, ComponentObject>> iter = components.entrySet().iterator();
					registerAllComponent(ctx, iter, promise);
				} catch (Exception e) {
					promise.fail(e);
				}
			}
		});
	}
	
	private void registerAllComponent(Context ctx, Iterator<Entry<String, ComponentObject>> iter, Promise<Void> promise) {
		if (iter.hasNext()) {
			Entry<String, ComponentObject> ent = iter.next();
			String key = ent.getKey();
			ComponentObject com = ent.getValue();
			
			ctx.components().get(key, com.getClassType()).onSuccess(obj -> {
				registerAllComponent(ctx, iter, promise);
			}).onFailure(e -> {
				promise.tryFail(e);
			});
		} else {
			promise.complete();
		}
	}

	public Future<RouteIntercepterImpl> createRouteIntercepters(Context ctx) {
		return Future.future(promise -> {
			try {
				RouteIntercepterImpl intercept = new RouteIntercepterImpl(ctx);
				
				if (routeIntercepters.size() < 1) {
					promise.complete(intercept);
				} else {
					AnnoationUtils.sortByOrder(routeIntercepters);
					Iterator<Class<?>> iter = routeIntercepters.iterator();
					eachRouteIntercepters(ctx, intercept, iter, promise);
				}
			} catch (Exception e) {
				promise.fail(e);
			}
		});
	}

	@SuppressWarnings("unchecked")
	private void eachRouteIntercepters(Context ctx, RouteIntercepterImpl intercept, Iterator<Class<?>> iter,
			Promise<RouteIntercepterImpl> promise) {
		if (iter.hasNext()) {
			Class<?> cls = iter.next();
			
			intercept.push((Class<? extends RouteIntercepter>) cls).onSuccess(v -> {
				eachRouteIntercepters(ctx, intercept, iter, promise);
			}).onFailure(promise::fail);
		} else {
			promise.complete(intercept);
		}
	}

	public void addAll(ArrayList<String> clazzList) throws Throwable {
		Iterator<String> iter = clazzList.iterator();
		
		while (iter.hasNext()) {
			add(iter.next());
		}
	}

	public List<Class<?>> getRouteList() {
		return routes;
	}

	public Future<Void> createWebSockets(Context ctx, HttpServer owner) {
		return Future.future(promise -> {
			if (websockets.size() < 1) {
				promise.complete();
			} else {
				try {
					owner.webSocketHandler(server -> {
						String path = server.path();
						Class<?> cls = null;
						
						if (websockets.containsKey(path)) {
							cls = websockets.get(path);
						} else {
							Iterator<String> iter = websockets.keySet().iterator();
							
							while (iter.hasNext()) {
								String key = iter.next();
								
								if (Pattern.matches(key, path)) {
									cls = websockets.get(key);
									break;
								}
							}
						}
						
						if (cls == null) {
							server.close();
						} else {
							try {
								Object instance = ClassUtils.newInstance(cls);
								
								ClassUtils.injectContext(instance, ctx).onSuccess(v -> {
									try {
										Method method = WebSocket.class.getDeclaredMethod("open", ServerWebSocket.class);
										method.invoke(instance, server);
									} catch (Exception e) {
										log.error("Call websocket function error", e);
										server.close();
									}
									
									server.handler(buffer -> {
										try {
											Method method = WebSocket.class.getDeclaredMethod("message", ServerWebSocket.class, Buffer.class);
											method.invoke(instance, server, buffer);
										} catch (Exception e) {
											log.error("Call websocket function error", e);
											server.close();
										}
									});
									
									server.exceptionHandler(error -> {
										try {
											Method method = WebSocket.class.getDeclaredMethod("error", ServerWebSocket.class, Throwable.class);
											method.invoke(instance, server, error);
										} catch (Exception e) {
											log.error("Call websocket function error", e);
											server.close();
										}
									});
									
									server.closeHandler(v1 -> {
										try {
											Method method = WebSocket.class.getDeclaredMethod("close", ServerWebSocket.class);
											method.invoke(instance, server);
										} catch (Exception e) {
											log.error("Call websocket function error", e);
											server.close();
										}
									});
								}).onFailure(e -> {
									log.error("Error creating websocket", e);
									server.close();
								});
							} catch (Exception e) {
								log.error("Error creating websocket", e);
								server.close();
							}
						}
					});
					
					promise.complete();
				} catch (Exception e) {
					promise.fail(e);
				}
			}
		});
	}

	public Future<Void> initVerticles(Context ctx) {
		return Future.future(promise -> {
			try {
				Method optionsMethod = ExtendVerticle.class.getDeclaredMethod("getDeploymentOptions");
				
				for (Class<?> cls : verticles) {
					Object instance = ClassUtils.newInstance(cls);
					
					ClassUtils.injectContext(instance, ctx).onSuccess(v -> {
						try {
							DeploymentOptions options = (DeploymentOptions) optionsMethod.invoke(instance);
							
							if (options == null) {
								ctx.vertx().deployVerticle((Verticle) instance);
							} else {
								ctx.vertx().deployVerticle((Verticle) instance, options);
							}
						} catch (Exception e) {
							promise.fail(e);
						}
					}).onFailure(promise::fail);
					
					if (promise.tryComplete()) {
						break;
					}
				}
				
				if (!promise.tryComplete()) {
					promise.complete();
				}
			} catch (Exception e) {
				promise.fail(e);
			}
		});
	}

	public ComponentObject removeComponent(String key) {
		synchronized (components) {
			if (components.containsKey(key)) {
				return components.remove(key);
			} else {
				return null;
			}
		}
	}

	public Future<Void> execRouterConfiguraters(Context ctx, Router router) {
		return Future.future(promise -> {
			try {
				Iterator<Class<?>> iter = routerConfiguraters.iterator();
				Method method = RouterConfigurater.class.getDeclaredMethod("init", Router.class);
				_execRouterConfigurater(promise, ctx, iter, method, router);
			} catch (Exception e) {
				promise.fail(e);
			}
		});
	}

	@SuppressWarnings("unchecked")
	private void _execRouterConfigurater(Promise<Void> promise, Context ctx, Iterator<Class<?>> iter, Method method, Router router) {
		if (iter.hasNext()) {
			try {
				Class<?> cls = iter.next();
				Object instance = ClassUtils.newInstance(cls);
				
				ClassUtils.injectContext(instance, ctx).onSuccess(v -> {
					try {
						Future<Void> f = (Future<Void>) method.invoke(instance, router);
						
						f.onSuccess(v1 -> {
							_execRouterConfigurater(promise, ctx, iter, method, router);
						}).onFailure(promise::fail);
					} catch (Exception e) {
						promise.fail(e);
					}
				}).onFailure(promise::fail);
			} catch (Exception e) {
				promise.fail(e);
			}
		} else {
			promise.complete();
		}
	}
	
}
